using UnityEngine;
using Framework.Core;
using System.Collections.Generic;
using System;

namespace Framework
{
    public enum UIStatus : byte
    {
        None,
        Load,
        Loaded,
        Idle,
        Prepare,
        Open,
        OpenAfter,
        Close,
        CloseAfter,
    }
    public class UGUIManager //: EventDispatcher
    {
        public bool Enabled
        {
            get
            {
                return _currentFrame > 1 && Config != null && AssetLoader != null;
            }
        }
        public UGUIScreenInfo ScreenInfo { get; private set; }
        public int SortingOrderBase { get; private set; }
        public int SortingOrderLayerStep { get; private set; }
        public int SortingOrderUIStep { get; private set; }
        public IUGUIManagerConfig Config { get; private set; }

        internal AssetLoader AssetLoader;
        private int _currentFrame = -1;

        private readonly List<UGUIInfo> _guiList = new List<UGUIInfo>();
        private readonly Dictionary<string, UGUIInfo> _guiInfo = new Dictionary<string, UGUIInfo>(StringComparer.Ordinal);
        private readonly List<UGUIInfo> _processInfo = new List<UGUIInfo>();
        private readonly List<UGUIInfo> _closedList = new List<UGUIInfo>();

        private readonly Features _features = new Features();
        private readonly Dictionary<int, LayerInfo> _layerInfo = new Dictionary<int, LayerInfo>();
        private bool _resortLayer = false;
        private int _opaqueLayerChanged;
        private int _maxLayer;
        private int _resizeFrame;
        private IScreenSafeAreaModifier _screenSafeAreaModifier;
        public void Initialize(IUGUIManagerConfig config)
        {
            Config = config;
            _maxLayer = 0;
            SortingOrderBase = 0;
            SortingOrderLayerStep = 200;
            SortingOrderUIStep = 10;
            ScreenInfo = new UGUIScreenInfo(_features, config);
            var rect = config.GetCanvas().GetComponent<RectTransform>();
            ScreenInfo.Update(_features, Mathf.FloorToInt(config.GetDesignSize().x), Mathf.FloorToInt(config.GetDesignSize().y), Screen.orientation, new Vector2(rect.rect.width, rect.rect.height));
        }
        public void Initialize(AssetLoader loader)
        {
            AssetLoader = loader;
        }
        /// <summary>
        /// 界面里 Canvas.sortingOrder的配制
        /// </summary>
        /// <param name="orderBase">开始值</param>
        /// <param name="orderLayerStep">每个层级的间隔</param>
        /// <param name="orderUIStep">每个window的间隔</param>
        public void Initialize(int orderBase = 0, int orderLayerStep = 1000, int orderUIStep = 50)
        {
            SortingOrderBase = orderBase;
            SortingOrderLayerStep = orderLayerStep;
            SortingOrderUIStep = orderUIStep;
            _opaqueLayerChanged = 1;
            _resortLayer = true;
        }
        public void AddFeature(IUGUIFeature value)
        {
            if (value == null) return;
            _features.Add(value);
        }
        public void SetModifier(IScreenSafeAreaModifier value)
        {
            _screenSafeAreaModifier = value;
        }
        public void Update(float deltaTime, float unscaledDeltaTime)
        {
            var frame = Time.frameCount;
            if (frame == _currentFrame) return;
            _currentFrame = frame;
            if (!Enabled) return;
            _features.OnProcessing(_processInfo.Count);
            var len = _guiList.Count;
            while (len-- > 0)
            {
                var info = _guiList[len];
                if (!info.OpenState) continue;
                if (info.Target == null) continue;
                if (info.ResizeFrame != _resizeFrame)
                {
                    info.ResizeFrame = _resizeFrame;
                    info.Target.SetScreenInfo(info, ScreenInfo);
                }
                info.Update(deltaTime);
            }
            len = _processInfo.Count;
            for (int i = 0; i < len; i++)
            {
                var info = _processInfo[i];

                if (info.Destoryed)
                {
                    FrameworkLog.LogError(info.Path + " has destory");
                    info.Working = false;
                    continue;
                }

                if (info.Status == UIStatus.None)
                {
                    if (info.OpenState)
                    {
                        //FrameworkLog.LogFormat("{0} {1}", info.Path, UIStatus.Load.ToString());
                        info.Load();
                    }
                    else
                    {
                        info.Working = false;
                    }
                }
                if (info.Status == UIStatus.Loaded)
                {
                    if (!info.Enabled)
                    {
                        info.Working = false;
                        continue;
                    }
                    info.Awake();
                    if (info.OpenState)
                    {
                        info.Target.ClearWorking();

                        _resortLayer = true;
                        _features.SetStatus(info, UIStatus.Prepare, UIStatus.Loaded);
                    }
                    else
                    {
                        info.Target.ClearWorking();
                        _opaqueLayerChanged = 1;
                        _features.SetStatus(info, UIStatus.Close, UIStatus.Loaded);
                    }

                }
                if (info.Status == UIStatus.Idle)
                {
                    if (!info.Enabled)
                    {
                        info.Working = false;
                        continue;
                    }
                    if (info.OpenState)
                    {
                        info.Target.ClearWorking();
                        _resortLayer = true;
                        _features.SetStatus(info, UIStatus.Prepare, UIStatus.Idle);
                    }
                    else
                    {
                        info.Target.ClearWorking();
                        _opaqueLayerChanged = 1;
                        _features.SetStatus(info, UIStatus.Close, UIStatus.Idle);
                    }
                }
                if (info.Status == UIStatus.Prepare)
                {
                    if (info.OpenState)
                    {
                        if (!info.Target.Working)
                        {
                            // info.Target.gameObject.SetActive(true);
                            // info.Target.Canvas.enabled=true;
                            info.SetParam();
                            info.Target.transform.localPosition = new Vector3(0, 0, 0);
                            _features.SetStatus(info, UIStatus.Open, UIStatus.Prepare);
                            continue;
                        }
                    }
                    else
                    {
                        info.Target.ClearWorking();
                        _features.SetStatus(info, UIStatus.Idle, UIStatus.Prepare);
                    }
                }
                if (info.Status == UIStatus.Open)
                {
                    if (info.OpenState)
                    {
                        if (!info.Target.Working)
                        {
                            _features.SetStatus(info, UIStatus.OpenAfter, UIStatus.Open);
                        }
                    }
                    else
                    {
                        info.Target.ClearWorking();
                        _features.SetStatus(info, UIStatus.Idle, UIStatus.Open);
                    }
                }
                if (info.Status == UIStatus.OpenAfter)
                {
                    _features.SetStatus(info, UIStatus.Idle, UIStatus.OpenAfter);
                    if (info.OpenState)
                    {
                        info.Working = false;
                        if (info.Enabled)
                        {
                            info.OpenAfter();
                            _opaqueLayerChanged = 1;
                        }
                    }
                }
                if (info.Status == UIStatus.Close)
                {
                    if (!info.Enabled)
                    {
                        info.Working = false;
                        continue;
                    }
                    if (info.OpenState)
                    {
                        _features.SetStatus(info, UIStatus.Idle, UIStatus.Close);
                    }
                    else
                    {
                        if (!info.Target.Working)
                        {
                            // info.Target.gameObject.SetActive(false);
                            // info.Target.Canvas.enabled=false;
                            info.Target.transform.localPosition = Config.GetInstanceRoot().localPosition;
                            _features.SetStatus(info, UIStatus.CloseAfter, UIStatus.Close);
                        }
                    }
                }
                if (info.Status == UIStatus.CloseAfter)
                {
                    _features.SetStatus(info, UIStatus.Idle, UIStatus.CloseAfter);
                    if (!info.OpenState)
                    {
                        info.CloseAfter();
                        info.Working = false;
                        // info.Target.SetShowSortingOrder(int.MaxValue);
                        if (info.DestroyWaitTime >= 0)
                        {
                            info.CloseTime = info.DestroyWaitTime;
                            if (!_closedList.Contains(info))
                            {
                                _closedList.Add(info);
                            }
                        }

                    }
                }
                if (!info.Enabled || info.Target.Working)
                {
                    break;
                }
            }
            len = _processInfo.Count;
            while (len-- > 0)
            {
                var info = _processInfo[len];
                if (!info.Working)
                {
                    _processInfo.RemoveAt(len);
                }
            }
            if (_resortLayer)
            {
                _resortLayer = false;
                foreach (var item in _layerInfo)
                {
                    item.Value.Clear();
                }
                foreach (var item in _guiInfo)
                {
                    var info = item.Value;
                    if (!info.Enabled) continue;
                    if (info.Target == null) continue;
                    // if (!info.Target.gameObject.activeSelf) continue;
                    if (!_layerInfo.TryGetValue(info.Target.Layer, out LayerInfo layerInfo))
                    {
                        layerInfo = new LayerInfo();
                        _layerInfo.Add(info.Target.Layer, layerInfo);
                    }
                    layerInfo.Add(info);
                    var layer = info.Layer;
                    if (layer < 0)
                    {
                        layer = info.Target.Layer;
                    }
                    var parent = Config.GetLayerContainer(layer);
                    if (parent != null)
                    {
                        if (info.Target.transform.parent != parent)
                        {
                            info.Target.transform.SetParent(parent, false);
                        }
                    }
                    else
                    {
                        FrameworkLog.LogError(info.Path + " not find layer " + layer);
                    }
                }
                foreach (var item in _layerInfo)
                {
                    var start = SortingOrderBase + SortingOrderLayerStep * item.Key;
                    item.Value.ResortLayer(start, SortingOrderUIStep, _maxLayer);
                }
                _opaqueLayerChanged = 1;
            }
            if (_opaqueLayerChanged > 0)
            {
                _opaqueLayerChanged--;
                if (_opaqueLayerChanged < 1)
                {
                    _maxLayer = SortingOrderBase - 1;
                    foreach (var item in _guiInfo)
                    {
                        var info = item.Value;
                        if (!info.Enabled) continue;
                        if (info.Target == null) continue;
                        if (!info.Target.Opaque) continue;
                        if (!info.OpenState)
                        {
                            if (info.Status == UIStatus.Idle)
                            {
                                continue;
                            }
                        }
                        if (info.Status == UIStatus.Idle)
                        {
                            var sortingOrder = info.Target.Canvas.sortingOrder;
                            if (sortingOrder > _maxLayer)
                            {
                                _maxLayer = sortingOrder;
                            }
                        }
                    }
                    foreach (var item in _guiInfo)
                    {
                        var info = item.Value;
                        if (!info.Enabled) continue;
                        if (info.Target == null) continue;
                        if (!info.OpenState) continue;

                        var status = info.Target.SetShowSortingOrder(_maxLayer);
                        _features.SetShowStatus(info, status);
                    }
                    _features.OnResort();
                }
            }

            if (ScreenInfo.Width != Screen.width || ScreenInfo.Height != Screen.height || ScreenInfo.Orientation != Screen.orientation || ScreenInfo.Dirty)
            {
                ScreenInfo.Dirty = false;
                var rect = this.Config.GetCanvas().GetComponent<RectTransform>();
                var size = rect.sizeDelta;
                ScreenInfo.Update(_features, Screen.width, Screen.height, Screen.orientation, size);
                var safeArea = Screen.safeArea;
                if (_screenSafeAreaModifier != null)
                {
                    safeArea = _screenSafeAreaModifier.Modify(safeArea, Screen.orientation, size);
                }

                var AnchorMin = new Vector2(safeArea.xMin / ScreenInfo.Width, safeArea.yMin / ScreenInfo.Height);
                var AnchorMax = new Vector2(safeArea.xMax / ScreenInfo.Width, safeArea.yMax / ScreenInfo.Height);

                var SafeArea = new Rect(AnchorMin.x * size.x, AnchorMin.y * size.y, (AnchorMax.x - AnchorMin.x) * size.x, (AnchorMax.y - AnchorMin.y) * size.y);
                ScreenInfo.Update(_features, AnchorMin, AnchorMax, SafeArea);
                _resizeFrame = Time.frameCount;
            }
            if (_processInfo.Count < 1)
            {
                len = _closedList.Count;
                while (len-- > 0)
                {
                    var info = _closedList[len];
                    if (info.OpenState || info.Status != UIStatus.Idle)
                    {
                        _closedList.RemoveAt(len);
                        continue;
                    }
                    info.CloseTime -= deltaTime;
                    if (info.CloseTime < 0)
                    {
                        _closedList.RemoveAt(len);
                        _guiList.Remove(info);
                        _guiInfo.Remove(info.Path);
                        info.Destory();
                        continue;
                    }
                }
            }
        }
        private UGUIInfo GetInfo(string path)
        {
            if (!_guiInfo.TryGetValue(path, out UGUIInfo info))
            {
                info = new UGUIInfo(this);
                _guiInfo.Add(path, info);
                _guiList.Add(info);
                info.Path = path;
            }
            return info;
        }
        private UGUIInfo InnerOpen(string path)
        {
            var info = GetInfo(path);
            if (info.OpenState) return info;
            _features.SetStatus(info, UIStatus.Open, UIStatus.None, false);
            info.Open();
            _processInfo.Remove(info);
            _processInfo.Add(info);
            return info;
        }
        public void Open(string path)
        {
            InnerOpen(path);
        }
        public void Open(string path, string paramName = null, object paramValue = null)
        {
            InnerOpen(path).SetParam(paramName, paramValue);
        }
        public void Close(string path)
        {
            if (_guiInfo.TryGetValue(path, out UGUIInfo info))
            {
                if (!info.OpenState) return;
                _features.SetStatus(info, UIStatus.Close, UIStatus.None, false);
                info.Close();
                if (!_processInfo.Contains(info))
                {
                    _processInfo.Add(info);
                }
            }
        }
        public void SetParam(string path, string name, object value = null)
        {
            GetInfo(path).SetParam(name, value);
        }
        public void SetLayer(string path, int layer)
        {
            var info = GetInfo(path);
            if (layer == info.Layer) return;
            info.Layer = layer;
            if (!info.Enabled) return;
            _resortLayer = true;
        }
        public void Close(int layer)
        {
            foreach (var item in _guiInfo)
            {
                var info = item.Value;
                if (!info.Enabled) continue;
                if (info.Target.Layer != layer) continue;
                if (!info.OpenState) continue;
                Close(info.Path);
            }
        }
        public bool IsOpen(string path)
        {
            if (_guiInfo.TryGetValue(path, out UGUIInfo info))
            {
                return info.IsOpen();
            }
            return false;
        }
        public bool IsClose(string path)
        {
            if (_guiInfo.TryGetValue(path, out UGUIInfo info))
            {
                return info.IsClose();
            }
            return true;
        }
        public void GetOpenWindows(List<UGUIWindow> list, int layer = -1)
        {
            list.Clear();
            foreach (var item in _guiInfo)
            {
                var info = item.Value;
                if (!info.Enabled) continue;
                if (info.Target == null) continue;
                if (!info.OpenState) continue;
                if (layer != -1)
                {
                    if (info.Target.Layer != layer) continue;
                }
                list.Add(info.Target);
            }
        }
        public void PreLoad(string path)
        {
            var info = GetInfo(path);
            info.Load();
        }
        internal class UGUIParam
        {
            public int Order;
            public string Name;
            public object Value;
        }
        internal class Features
        {
            private LinkedList<IUGUIFeature> _list;
            public Features()
            {
                _list = new LinkedList<IUGUIFeature>();
            }
            public void Add(IUGUIFeature value)
            {
                _list.AddLast(value);
            }
            public void SetStatus(UGUIInfo target, UIStatus value, UIStatus previous, bool updateTarget = true)
            {
                if (updateTarget)
                {
                    //FrameworkLog.LogFormat("{0} {1}", target.Path, value.ToString());
                    target.SetStatus(value, previous);
                }
                if (!target.Enabled || target.Target.Window == null) return;
                foreach (var item in _list)
                {
                    item.SetStatus(target.Target.Window, value, previous);
                }
            }
            public void SetShowStatus(UGUIInfo target, UIStatus value)
            {
                if (value == UIStatus.Idle) return;
                if (!target.Enabled || target.Target.Window == null) return;
                foreach (var item in _list)
                {
                    item.SetShowStatus(target.Target.Window, value);
                }
            }
            public void OnProcessing(int count)
            {
                foreach (var item in _list)
                {
                    item.OnProcessing(count);
                }
            }
            public void OnResort()
            {
                foreach (var item in _list)
                {
                    item.OnResort();
                }
            }

        }
        internal class UGUIInfo
        {
            private static readonly CustomPool<UGUIParam> _paramPool = new(() => { return new UGUIParam(); }, 20);
            private static readonly List<UGUIParam> _paramList = new List<UGUIParam>();
            private static int _openOrder = 0;
            public bool Enabled { get; private set; }
            public bool Loaded { get; private set; }
            public bool Destoryed { get; private set; }
            public UGUIWindow Target { get; private set; }
            public float DestroyWaitTime
            {
                get
                {
                    if (!Enabled)
                    {
                        return -1;
                    }
                    if (Target == null)
                    {
                        return -1;
                    }
                    return Target.DestoryOnCloseWaitTime;
                }
            }
            public float CloseTime;
            public string Path;
            public int Layer;
            public bool Working;
            public int ResizeFrame;
            public bool OpenState { get; private set; }
            public int Frame { get; private set; }
            public UIStatus Status { get; private set; }
            private bool _awake;
            private readonly Dictionary<string, UGUIParam> _paramMap;
            private int _paramOrder;
            private bool _loading;
            private bool _loaded;
            private readonly UGUIManager _manager;
            private bool _isOpen;
            private bool _isClose;
            public UGUIInfo(UGUIManager manager)
            {
                Destoryed = false;
                _manager = manager;
                SetStatus(UIStatus.None, UIStatus.None);
                _paramMap = new Dictionary<string, UGUIParam>(StringComparer.Ordinal);
                _isOpen = false;
                _isClose = false;
                Layer = -1;
            }
            public void SetStatus(UIStatus value, UIStatus previous)
            {
                this.Status = value;
                if (Target == null) return;
                if (previous == UIStatus.None) return;
                Target.SetStatus(this, value, previous);
            }

            public void Open()
            {
                Working = true;
                OpenState = true;
                Frame = _openOrder++;
                _isClose = false;
                _isOpen = false;
                if (Destoryed)
                {
                    FrameworkLog.LogError("has destory");
                }
            }
            public void Close()
            {
                Working = true;
                OpenState = false;
                _isOpen = false;
                _isClose = false;
                if (Destoryed)
                {
                    FrameworkLog.LogError("has destory");
                }
            }
            public void OpenAfter()
            {
                _isOpen = true;
            }
            public void CloseAfter()
            {
                _isClose = true;
            }
            public bool IsOpen()
            {
                return _isOpen;
            }
            public bool IsClose()
            {
                return _isClose;
            }
            public void Awake()
            {
                if (_awake) return;
                if (Target == null) return;
                if (Target.Window == null) return;
                _awake = true;
                Target.SetSign(this);
                if (Target.Window != null)
                {
                    Target.Window.Path = this.Path;
                }
                Target.SetCloseAction(this, () =>
                {
                    _manager.Close(this.Path);
                });
            }
            public void Load()
            {
                if (_loaded || _loading)
                {
                    return;
                }
                _loading = true;
                Status = UIStatus.Load;
                _manager.AssetLoader.LoadAsset(Path, result =>
                {
                    if (result.Asset is GameObject obj)
                    {
                        GameObject go = null;
                        if (!result.IsGameObject)
                        {
                            go = GameObject.Instantiate(obj, this._manager.Config.GetInstanceRoot(), false);
                        }
                        else
                        {
                            go = obj;
                            go.transform.SetParent(this._manager.Config.GetInstanceRoot(), false);
                        }
                        go.transform.localPosition = this._manager.Config.GetInstanceRoot().localPosition;
                        //FrameworkLog.LogFormat("{0} loaded", this.Path);
                        // go.SetActive(false);
                        this.Target = go.GetComponent<UGUIWindow>();
                        if (Target != null)
                        {
                            this.Enabled = true;
                            Loaded = true;
                        }
                    }
                    SetStatus(UIStatus.Loaded, UIStatus.None);
                    _loaded = true;
                    _loading = false;
                });
            }
            public void SetParam(string name, object value)
            {
                if (string.IsNullOrEmpty(name)) return;
                if (Target != null && Target.Widget != null && Target.Widget.Enabled)
                {
                    Target.SetParam(name, value);
                    return;
                }
                if (!_paramMap.TryGetValue(name, out UGUIParam data))
                {
                    data = _paramPool.Get();
                    _paramMap.Add(name, data);
                }
                data.Name = name;
                data.Value = value;
                data.Order = _paramOrder++;
            }
            public void SetParam()
            {
                if (Target == null || Target.Widget == null || !Target.Widget.Enabled)
                {
                    return;
                }
                if (_paramMap.Count < 1) return;
                _paramList.Clear();
                foreach (var item in _paramMap)
                {
                    _paramList.Add(item.Value);
                }
                _paramMap.Clear();
                _paramList.Sort(sortParam);
                foreach (var item in _paramList)
                {
                    Target.SetParam(item.Name, item.Value);
                    _paramPool.Release(item);
                }
                _paramList.Clear();
            }
            public bool Destory()
            {
                Destoryed = true;
                Enabled = false;
                if (Target == null)
                {
                    return false;
                }
                GameObject.Destroy(Target.gameObject);
                return true;
            }
            public void Update(float deltaTime)
            {
                if (!OpenState) return;
                if (!_isOpen) return;
                if (Target == null || Target.Widget == null || !Target.Widget.Enabled)
                {
                    return;
                }
                SetParam();
                if (!Target.enabled) return;
                if (Target.Working) return;
                Target.Window.Update(this, deltaTime);
            }
            private int sortParam(UGUIParam a, UGUIParam b)
            {
                return a.Order - b.Order;
            }
        }
        internal class LayerInfo
        {
            private readonly List<UGUIInfo> _list = new List<UGUIInfo>();
            public void Clear()
            {
                _list.Clear();
            }
            public void Add(UGUIInfo value)
            {
                _list.Add(value);
            }
            public void ResortLayer(int start, int step, int maxOrder)
            {
                _list.Sort(Sort);
                int offset = 0;
                var len = _list.Count;
                for (int i = 0; i < len; i++)
                {
                    var order = start + offset;
                    var w = _list[i].Target;
                    w.SetSortingOrder(order);
                    offset += step;
                }
            }
            private int Sort(UGUIInfo a, UGUIInfo b)
            {
                return a.Frame - b.Frame;
            }
        }
    }
}